/****************************************************************************
  SCC.cpp for smse2k
  based on emu2212.c -- S.C.C. emulator by Mitsutaka Okazaki 2001

  TEST ME TEST ME TEST ME TEST ME TEST ME TEST ME TEST ME

  2001 09-30 : Version 1.00
  2001 10-03 : Version 1.01 -- Added SCC_set_quality().
  2002 02-14 : Version 1.10 -- Added SCC_writeReg(), SCC_set_type().
                               Fixed SCC_write().
  2002 02-17 : Version 1.11 -- Fixed SCC_write().
  2002 03-02 : Version 1.12 -- Removed SCC_init & SCC_close.
  2003 09-19 : Version 1.13 -- Added SCC_setMask() and SCC_toggleMask()
  2004 10-21 : Version 1.14 -- Fixed the problem where SCC+ is disabled.

  uf's modifications:
  07/19/2008 - Implemented auto-detection algorithm. (unknownfile)
  08/10/2008 - Full stereo implemented.
  12/11/2009 - Port to smse2k. C++ified.

  Registar map for SCC_writeReg()

  $00-1F : WaveTable CH.A
  $20-3F : WaveTable CH.B
  $40-5F : WaveTable CH.C
  $60-7F : WaveTable CH.D&E(SCC), CH.D(SCC+)
  $80-9F : WaveTable CH.E
 
  $C0    : CH.A Freq(L)
  $C1    : CH.A Freq(H)
  $C2    : CH.B Freq(L)
  $C3    : CH.B Freq(H)
  $C4    : CH.C Freq(L)
  $C5    : CH.C Freq(H)
  $C6    : CH.D Freq(L)
  $C7    : CH.D Freq(H)
  $C8    : CH.E Freq(L)
  $C9    : CH.E Freq(H)

  $D0    : CH.A Volume
  $D1    : CH.B Volume
  $D2    : CH.C Volume
  $D3    : CH.D Volume
  $D4    : CH.E Volume
 
  $E0    : Bit0 = 0:SCC, 1:SCC+
  $E1    : CH mask
  $E2    : Extra Flags

*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "smse2kincludes.h"
#include "sounddevicefactory.h"
#include "SCC.h"

#define GETA_BITS 22

void CKonamiSCC::internal_refresh () {
  if (quality)
  {
    base_incr = 2 << GETA_BITS;
    realstep = (UINT32) ((1 << 31) / rate);
    sccstep = (UINT32) ((1 << 31) / (clk / 2));
    scctime = 0;
  }
  else
  {
    base_incr = (UINT32) ((double) clk * (1 << GETA_BITS) / rate);
  }
}

/*
UINT32 SCC_setMask (SCC *scc, UINT32 mask)
{
  UINT32 ret = 0;
  if(scc)
  {
    ret = scc->mask;
    scc->mask = mask;
  }  
  return ret;
}

 UINT32
SCC_toggleMask (SCC *scc, UINT32 mask)
{
  UINT32 ret = 0;
  if(scc)
  {
    ret = scc->mask;
    scc->mask ^= mask;
  }
  return ret;
}
*/

void CKonamiSCC::SetQuality(UINT32 q)
{
  quality = q;
  internal_refresh ();
}

void CKonamiSCC::SetRate(UINT32 r)
{
  this->rate = r ? r : 44100;
  internal_refresh ();
}

// TODO: Make this any sample rate
void CKonamiSCC::Init(CMixer* mixer, UINT32 clock) {

  this->clk = clock;
  this->rate = 44100; //srate ? srate : 44100;
  mixer->CreateNewBuffer(this, 44100);
  SetQuality (rate);
  this->type = SCC_ENHANCED;
  Reset();
}

void CKonamiSCC::Reset() {
  int i, j;


  this->checkDone=0;
  this->mode = 0;
  this->active = 0;
  this->base_adr = 0x9000;

  for (i = 0; i < 5; i++)
  {
    for (j = 0; j < 5; j++)
      this->wave[i][j] = 0;
    this->count[i] = 0;
    this->freq[i] = 0;
    this->phase[i] = 0;
    this->volume[i] = 0;
    this->offset[i] = 0;
    this->rotate[i] = 0;
  }

  memset(this->reg,0,0x100-0xC0);

  this->mask = 0;

  this->ch_enable = 0xff;
  this->ch_enable_next = 0xff;

  this->cycle_4bit = 0;
  this->cycle_8bit = 0;
  this->refresh = 0;

  this->out = 0;
  this->prev = 0;
  this->next = 0;

  return;
}

void CKonamiSCC::Shutdown() {
  
}

/*
void CKonamiSCC::SCC_set_pan (int ch, int pan) {

	if (ch > 5) ch = 5;
	this->pan[ch] = pan & 3;
}
*/

void CKonamiSCC::Mix_Frame(int in, int out[2], int pan, int invert) {
	int temp[2] = {0,0};
	int i=0;

	if (!in) return;

	// This is set to 128 because of as channel's tendency to mute when the panning is 127
	if(pan < 0) { 
		temp[0] = in;
		temp[1] = ((in * (128 + pan)) / 128);
	} else if(pan > 0) {
		temp[0] = ((in * (128 - pan)) / 128);
		temp[1] = in;
	} else if (pan == 0) {
		for (i=0;i<2;i++) temp[i] = in;
	}

	out[0] += temp[0];
	if (invert) out[1] -= temp[1];
	else out[1] += temp[1];
}

// calculation for stereo mode - unknownfile
// mostly modified from emu2413.c
INLINE void CKonamiSCC::calc_stereo (int output[2]) {
	int pcmdat[4] = { 0,0,0,0 }; // 0 = disregard, 1 = left, 2 = right, 3 = centre
	int i;
	int temp;
	
	// HOLY COPYPASTE BATMAN

	for (i=0;i<2;i++) output[i]=0;

	//scc->invert[3]=1;
	for (i = 0; i < 5; i++) {
		this->count[i] = (this->count[i] + this->incr[i]);

		if (this->count[i] & (1 << (GETA_BITS + 5))) {
			this->count[i] &= ((1 << (GETA_BITS + 5)) - 1);
			this->offset[i] = (this->offset[i] + 31) & this->rotate[i];
			this->ch_enable &= ~(1 << i);
			this->ch_enable |= this->ch_enable_next & (1 << i);
		}

		if (this->ch_enable & (1 << i)) {
			this->phase[i] = ((this->count[i] >> (GETA_BITS)) + this->offset[i]) & 0x1F;
			if(!(this->mask&SCC_MASK_CH(i))) {
				temp=((((INT8) (this->wave[i][this->phase[i]]) * (INT8) this->volume[i])));
				Mix_Frame(temp,pcmdat,this->pan[i],0);//this->invert[i]);
			}
		}
	}

  // output 'em
  output[1] = pcmdat[1];//(pcmdat[1] + pcmdat[3]) << 4;
  output[0] = pcmdat[0];//(pcmdat[2] + pcmdat[3]) << 4;

}

// lol opll_calc_stereo
int CKonamiSCC::Buffer(short** buf, int length) {
	short* output[2] = {  buf[0], buf[1] };
	int  outbuf[2];

	for (int i=0;i<length;i++) {
		if (!this->quality) {
			calc_stereo(outbuf);
			*(output[0]) = (short) (outbuf[0]);
			*(output[1]) = (short) (outbuf[1]);
			//return;
		} else {
			// resamplan gaems
			while (this->realstep > this->scctime) {
				this->scctime+=this->sccstep;
				calc_stereo ( this->soutar);
				this->sout1 = this->soutar[0];
				this->sout2 = this->soutar[1];
			}
			this->scctime -= this->realstep;
			*(output[0]) = (short) (this->sout1);
			*(output[1]) = (short) (this->sout2);
		}
		output[0]++;
		output[1]++;
	}

	return length;
}


UINT32 CKonamiSCC::ReadReg(UINT32 adr)
{
  if (adr < 0xA0)
    return this->wave[adr >> 5][adr & 0x1f];
  else if( 0xC0 < adr && adr < 0xF0 )
    return this->reg[adr-0xC0];
  else
    return 0;
}

void CKonamiSCC::WriteReg (UINT32 adr, UINT32 val)
{
  int ch,i;
  UINT32 freq;

  adr &= 0xFF;

  if (adr < 0xA0)
  {
    ch = (adr & 0xF0) >> 5;
	//cprintf("Wrote to channel %d\n", ch);
    if (!this->rotate[ch])
    {
		if (this->mode == 0 && ch == 3)
        this->wave[4][adr & 0x1F] = (char) (adr & 16) * 8;//val;
		this->wave[ch][0x1f - (adr & 0x1F)] = (char) val;
		if (this->mode == 0 && ch == 3)
				this->wave[4][adr & 0x1F] = (char) val;
    }
  }
  else if (0xC0 <= adr && adr <= 0xC9)
  {
    this->reg[adr-0xC0] = val;
    ch = (adr & 0x0F) >> 1;
    if (adr & 1)
      this->freq[ch] = ((val & 0xF) << 8) | (this->freq[ch] & 0xFF);
    else
      this->freq[ch] = (this->freq[ch] & 0xF00) | (val & 0xFF);

	// Update auto-panning. Todo: Fix to make less shit.
	if (ch & 1) {
		this->pan[ch] = 0 + ((this->freq[ch] >> 3) & 0x7F);
	} else {
		this->pan[ch] = 0 - ((this->freq[ch] >> 3) & 0x7F);
		//if (this->freq[ch] < 1024) this->invert[ch] = 1;
		//else this->invert[ch] = 0;
		//if (this->freq[ch] > 64) this->pan[ch] ^= rand() & 0x80;
	}

    if (this->refresh)
      this->count[ch] = 0;
    freq = this->freq[ch];
    if (this->cycle_8bit)
      freq &= 0xFF;
    if (this->cycle_4bit)
      freq >>= 8;
    if (freq <= 8)
      this->incr[ch] = 0;
    else
      this->incr[ch] = this->base_incr / (freq + 1);
  }
  else if (0xD0 <= adr && adr <= 0xD4)
  {
    this->reg[adr-0xC0] = val;
    this->volume[adr & 0x0F] = (UINT8) (val & 0xF);
  }
  else if (adr == 0xE0)
  {
    this->reg[adr-0xC0] = val;
    this->mode = (UINT8) val & 1;
  }
  else if (adr == 0xE1)
  {
    this->reg[adr-0xC0] = val;
    this->ch_enable_next = (UINT8) val & 0x1F;
  }
  else if (adr == 0xE2)
  {
    this->reg[adr-0xC0] = val;
    this->cycle_4bit = val & 1;
    this->cycle_8bit = val & 2;
    this->refresh = val & 32;
    if (val & 64)
      for (ch = 0; ch < 5; ch++)
        this->rotate[ch] = 0x1F;
    else
      for (ch = 0; ch < 5; ch++)
        this->rotate[ch] = 0;
    if (val & 128)
      this->rotate[3] = this->rotate[4] = 0x1F;
  }

  return;
}

INLINE void CKonamiSCC::write_standard (UINT32 adr, UINT32 val)
{
  adr &= 0xFF;

  if (adr < 0x80)               /* wave */
  {
    WriteReg (adr, val);
  }
  else if (adr < 0x8A)          /* freq */
  {
    WriteReg (adr + 0xC0 - 0x80, val);
  }
  else if (adr < 0x8F)          /* volume */
  {
    WriteReg (adr + 0xD0 - 0x8A, val);
  }
  else if (adr == 0x8F)         /* ch enable */
  {
    WriteReg (0xE1, val);
  }
  else if (0xE0 <= adr)         /* flags */
  {
    WriteReg (0xE2, val);
  } else WriteReg (adr, val);
}

INLINE void CKonamiSCC::write_enhanced (UINT32 adr, UINT32 val)
{
  adr &= 0xFF;

  if (adr < 0xA0)               /* wave */
  {
    WriteReg (adr, val);
  }
  else if (adr < 0xAA)          /* freq */
  {
    WriteReg (adr + 0xC0 - 0xA0, val);
  }
  else if (adr < 0xAF)          /* volume */
  {
    WriteReg (adr + 0xD0 - 0xAA, val);
  }
  else if (adr == 0xAF)         /* ch enable */
  {
    WriteReg (0xE1, val);
  }
  else if (0xC0 <= adr && adr <= 0xDF)  /* flags */
  {
    WriteReg (0xE2, val);
  }
}

INLINE UINT32 CKonamiSCC::read_enhanced (UINT32 adr)
{
  adr &= 0xFF;
  if (adr < 0xA0)
    return ReadReg(adr);
  else if (adr < 0xAA)
    return ReadReg (adr + 0xC0 - 0xA0);
  else if (adr < 0xAF)
    return ReadReg (adr + 0xD0 - 0xAA);
  else if (adr == 0xAF)
    return ReadReg (0xE1);
  else if (0xC0 <= adr && adr <= 0xDF)
    return ReadReg (0xE2);
  else
    return 0;
}

INLINE UINT32 CKonamiSCC::read_standard (UINT32 adr)
{
  adr &= 0xFF;
  if(adr<0x80)
    return ReadReg(adr);
  else if (0xA0<=adr&&adr<=0xBF)
    return ReadReg(0x80+(adr&0x1F));
  else if (adr < 0x8A)          
    return ReadReg(adr + 0xC0 - 0x80);
  else if (adr < 0x8F)          
    return ReadReg(adr + 0xD0 - 0x8A);
  else if (adr == 0x8F)         
    return ReadReg(0xE1);
  else if (0xE0 <= adr)         
    return ReadReg(0xE2);
  else return 0;
}

UINT8 CKonamiSCC::Read(UINT64 adr) {
  if( (adr&0xFFFE) == 0xBFFE ) 
    return (this->base_adr>>8)&0x20;
  
  if( adr < this->base_adr ) return 0;
  adr -= this->base_adr;
  
  if( adr == 0 ) 
  {
    if(this->mode) return 0x80; else return 0x3F;
  }

  if(!this->active||adr<0x800||0x8FF<adr) return 0;

  switch (this->type) 
  {
  case SCC_STANDARD:
      return read_standard (adr);
    break;
  case SCC_ENHANCED:
    if(!this->mode)
      return read_standard (adr);
    else 
      return read_enhanced (adr);
    break;
  default:
    break;
  }

  return 0;
}

#define addr adr // GODDAMNIT

void CKonamiSCC::Write(UINT64 adr, UINT8 val) {
  val = val & 0xFF;

  /*
  ** Auto-detection algorithm for SCC-types. The initial write to the
  ** SCC's address space determines what type the chip is.
  ** (0xB000 and higher excluding 0xBFFE = enhanced SCC)
  */

  if (!this->checkDone && addr != 0xBFFE && ((adr & 0xFF) != 0)) {
		
	  if (adr > 0xB000) {
		 this->mode = 1;
		 this->type = SCC_ENHANCED;
		 this->base_adr = 0xB000;
	  } else { 
		 this->mode = 0;
		 this->type = SCC_STANDARD;
		 this->base_adr = 0x9000 ;
	 }

	 Msg("SCC check done. Type is %d, base address %04X\n",type,base_adr);
	 this->checkDone=1;
	 this->active = 1;
  }

  if( this->type == SCC_ENHANCED && (adr&0xFFFE) == 0xBFFE ) 
  {
    this->base_adr = 0x9000 | ((val&0x20)<<8);
    return;
  }

   if( adr < this->base_adr ) {
		Msg("Invalid SCC write %04X (base %04X)\n",adr,this->base_adr);
	  return;
  }  
  //Msg("SCC: Write at %04X\n",adr);
  adr -= this->base_adr;


  if(!this->active||adr<0x800||0x8FF<adr) return;

  switch (this->type) 
  {
  case SCC_STANDARD:
    write_standard (adr, val);
   break;
  case SCC_ENHANCED:
   if(this->mode)
      write_enhanced (adr, val);
   else
      write_standard (adr, val);
  default:
    break;
  }

  return;
}

// just one for now faggots
SOUND_FACTORY("SCC",1,CKonamiSCC);